package com.msh.artascope.devcloud.service.service.impl;


import com.msh.artascope.devcloud.client.po.ModuleNetworkPo;
import com.msh.artascope.devcloud.client.po.ModulePo;
import com.msh.artascope.devcloud.client.po.ModuleRunningPo;
import com.msh.artascope.devcloud.client.qo.ModuleNetworkQo;
import com.msh.artascope.devcloud.client.qo.ModuleReverseProxyQo;
import com.msh.artascope.devcloud.client.qo.ModuleRunningQo;
import com.msh.artascope.devcloud.service.dao.ModuleNetworkDao;
import com.msh.artascope.devcloud.service.define.DevCloudDef;
import com.msh.artascope.devcloud.service.model.query.ModuleNetworkQueryVo;
import com.msh.artascope.devcloud.service.model.query.ModuleReverseProxyQueryVo;
import com.msh.artascope.devcloud.service.model.vo.ModuleNetworkVo;
import com.msh.artascope.devcloud.service.model.vo.ModuleReverseProxyVo;
import com.msh.artascope.devcloud.service.service.KubernetesConfigService;
import com.msh.artascope.devcloud.service.service.ModuleNetworkService;
import com.msh.artascope.devcloud.service.service.ModuleReverseProxyService;
import com.msh.artascope.devcloud.service.service.ModuleRunningService;
import com.msh.artascope.devcloud.service.service.ModuleService;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioVsHttpMatchInfo;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioVsInfo;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.IstioVsRoute;
import com.msh.artascope.devcloud.service.wrapper.K8sWrapper.K8sInfo;
import com.msh.frame.client.base.TenantBaseServiceImpl;
import com.msh.frame.client.common.Result;
import com.msh.frame.client.exception.BizException;
import com.msh.frame.interfaces.IdGenerateable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

/**
 * @author shihu
 * @mail m-sh@qq.com
 */
@Service
@Slf4j
public class ModuleNetworkServiceImpl extends TenantBaseServiceImpl<ModuleNetworkPo,ModuleNetworkQo> implements ModuleNetworkService {

  @Resource
  private ModuleNetworkDao moduleNetworkDao;

  @Resource
  private IdGenerateable idGenerateable;

  @Autowired
  private K8sWrapper k8sWrapper;

  @Autowired
  private ModuleService moduleService;

  @Resource
  @Lazy
  private ModuleReverseProxyService moduleReverseProxyService;

  @Resource
  private KubernetesConfigService kubernetesConfigService;
  @Resource
  @Lazy
  private ModuleRunningService moduleRunningService;

  @Override
  public Long insertVo(ModuleNetworkPo param) {
    Long moduleId = param.getModuleId();
    Long tenantId = param.getTenantId();
    ModulePo modulePo = moduleService.get(moduleId, tenantId);
    BizException.doThrowIfNull("获取不到模块信息", modulePo);
    K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
    BizException.doThrowIfNull("缺少k8s配置信息", k8s);
    return insertWithK8sService(param, modulePo, k8s);
  }

  /**
   * 新增网络并创建k8s-service
   * @param param
   * @param modulePo
   * @param k8s
   * @return
   */
  private long insertWithK8sService(ModuleNetworkPo param, ModulePo modulePo,
      K8sWrapper.K8sInfo k8s) {
    long insert = this.insert(param);
    k8sWrapper.createService(k8s, modulePo, param);
    reloadVirtualServiceByModuleId(modulePo, param.getAppPort(), k8s, modulePo.getTenantId());
    return insert;
  }


  @Override
  public void reloadVirtualServiceByModuleId(ModulePo modulePo, K8sInfo k8s, Long tenantId) {
    IstioVsInfo istioVsInfo = buildIstioVsInfo(modulePo, null, k8s.getNamespace(), tenantId);
    k8sWrapper.saveIstioVirtualService(k8s, modulePo.getName(), istioVsInfo);
  }

  private void reloadVirtualServiceByModuleId(ModulePo modulePo, Integer port, K8sInfo k8s,
      Long tenantId) {
    IstioVsInfo istioVsInfo = buildIstioVsInfo(modulePo, port, k8s.getNamespace(), tenantId);
    k8sWrapper.saveIstioVirtualService(k8s, modulePo.getName(), istioVsInfo);
  }


  private IstioVsInfo buildIstioVsInfo(ModulePo modulePo, Integer port, String namespace,
      Long tenantId) {
    String moduleName = modulePo.getName();
    Long moduleId = modulePo.getId();
    IstioVsInfo istioVsInfo = new IstioVsInfo()
        .setName(moduleName)
        .setNamespace(namespace)
        .setDomain(moduleName);
    List<ModuleRunningPo> runningPoList = moduleRunningService.list(
        new ModuleRunningQo().setModuleId(moduleId).setTenantId(tenantId));
    if (CollectionUtils.isEmpty(runningPoList) || runningPoList.size() == 1) {
      if (null == port) {
        ModuleNetworkPo moduleNetworkPo = listOne(
            new ModuleNetworkQo().setModuleId(moduleId).setTenantId(tenantId));
        BizException.doThrowIfNull("无法获取制品网络配置", moduleNetworkPo);
        port = moduleNetworkPo.getAppPort();
      }
      IstioVsHttpMatchInfo match = new IstioVsHttpMatchInfo()
          .setRoute(
              Collections.singletonList(new IstioVsRoute().setHost(moduleName).setPort(port)));
      istioVsInfo.setMatchs(Collections.singletonList(match));
    } else {
      Map<String, IstioVsHttpMatchInfo> matchMap = new HashMap<>();
      runningPoList.stream()
          .forEach(r -> {
            IstioVsRoute istioVsRoute = new IstioVsRoute().setHost(moduleName)
                .setSubset(DevCloudDef.getVersion(r.getVersion()));
            IstioVsHttpMatchInfo istioVsHttpMatchInfo = matchMap.computeIfAbsent(
                r.getHeaderKey() + "#" + r.getHeaderValue(),
                o -> new IstioVsHttpMatchInfo().setHeaderKey(r.getHeaderKey())
                    .setHeaderValue(r.getHeaderValue()).setRoute(new LinkedList<>()));
            istioVsHttpMatchInfo.getRoute().add(istioVsRoute);
          });
      List<IstioVsHttpMatchInfo> list = matchMap.values().stream()
          .peek(IstioVsHttpMatchInfo::initWeight).sorted((o1, o2) -> {
            if (StringUtils.hasText(o1.getHeaderKey())) {
              return -1;
            } else if (StringUtils.hasText(o2.getHeaderKey())) {
              return 1;
            }
            return 0;
          }).collect(Collectors.toList());
      istioVsInfo.setMatchs(list);
    }
    return istioVsInfo;
  }

  @Override
  public long insert(ModuleNetworkPo param) {
    param.setId(idGenerateable.getUniqueId());
    return super.insert(param);
  }

  @Override
  @Transactional(rollbackFor = Exception.class)
  public void update(ModuleNetworkPo param) {
    Long id = param.getId();
    Long tenantId = param.getTenantId();
    ModuleNetworkPo db = this.get(id, tenantId);
    if(!db.getServicePort().equals(param.getServicePort())
        || !db.getAppPort().equals(param.getAppPort())
    ){
      if(!db.getServicePort().equals(param.getServicePort())) {
        int count = moduleReverseProxyService.count(
            new ModuleReverseProxyQo().setModuleId(db.getModuleId()).setTenantId(tenantId));
        BizException.doThrowIfTrue("必须删除路由配置才能修改服务端口", count > 0);
      }

      ModulePo modulePo = moduleService.get(param.getModuleId(), tenantId);
      BizException.doThrowIfNull("获取不到模块信息", modulePo);
      K8sInfo k8s = kubernetesConfigService.getK8s(modulePo.getEnvId(), modulePo.getTenantId());
      BizException.doThrowIfNull("缺少k8s配置信息", k8s);

      super.update(param);
      k8sWrapper.deleteService(k8s, modulePo);
      k8sWrapper.createService(k8s, modulePo, param);
      reloadVirtualServiceByModuleId(modulePo, param.getAppPort(), k8s, modulePo.getTenantId());
    }
  }

  @Override
  public int insertCollection(Collection<ModuleNetworkPo> param) {
    if(CollectionUtils.isEmpty(param)){
      return 0;
    }
    Long[] uniqueIds = idGenerateable.getUniqueIds(param.size());
    int i=0;
    for (ModuleNetworkPo po : param) {
      po.setId(uniqueIds[i++]);
    }
    return super.insertCollection(param);
  }

  // ----------前端vo处理-------------------------------------------------------

  private ModuleNetworkVo toVo(ModuleNetworkPo po){
    ModuleNetworkVo vo = po.convert(ModuleNetworkVo.class);
    return vo;
  }

  private List<ModuleNetworkVo> toVoList(List<ModuleNetworkPo> list){
    if(CollectionUtils.isEmpty(list)){
      return Collections.emptyList();
    }

    return list.stream().map(po->{
      return toVo(po);
    }).collect(Collectors.toList());
  }

  @Override
  public ModuleNetworkVo getVo(Long id, Long tenantId) {
    ModuleNetworkPo po = this.get(id, tenantId);
    if(null == po){
      return null;
    }
    ModuleNetworkVo vo = toVo(po);

    return vo;
  }

  @Override
  public ModuleNetworkVo getVoByModuleId(Long moduleId, Long tenantId) {
    ModuleNetworkPo po = this.listOne(new ModuleNetworkQo().setModuleId(moduleId));
    if (null == po) {
      return null;
    }
    ModuleNetworkVo vo = po.convert(ModuleNetworkVo.class);

    List<ModuleReverseProxyVo> moduleReverseProxyVoList = moduleReverseProxyService
        .listVo(
            (ModuleReverseProxyQueryVo) new ModuleReverseProxyQueryVo().setModuleId(moduleId)
                .setTenantId(tenantId));

    vo.setModuleReverseProxyList(moduleReverseProxyVoList);
    return vo;
  }

  @Override
  public List<ModuleNetworkVo> listVo(ModuleNetworkQueryVo q){
    ModuleNetworkQo qo = q.convert(ModuleNetworkQo.class);
    List<ModuleNetworkPo> poList = this.list(qo);
    List<ModuleNetworkVo> voList = toVoList(poList);

    return voList;
  }

  @Override
  public Result<List<ModuleNetworkVo>> pageVo(ModuleNetworkQueryVo param) {
    ModuleNetworkQo qo = param.convert(ModuleNetworkQo.class);
    int count = this.count(qo);
    if (0 == count) {
      return Result.successReturn(Collections.<ModuleNetworkVo>emptyList()).setCount(count);
    }
    List<ModuleNetworkPo> poList = this.list(qo);
    List<ModuleNetworkVo> voList = toVoList(poList);

    return Result.successReturn(voList).setCount(count);
  }

}
